///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef __BUOLA_OS_CPROCESS_H__
#define __BUOLA_OS_CPROCESS_H__

#include <buola/buola.h>
#include <buola/utility/usignal.h>
#include <buola/io/cfdstream.h>
#include <buola/io/curi.h>
#include <map>

namespace buola {

///\addtogroup os
///@{

////////////////////////////////////////////////////////////////////////////

/// run external process

/// Allows to run an external process, control its execution and access its
/// input and output streams
///
////////////////////////////////////////////////////////////////////////////

class CProcess : public UUncopiable
{
    struct SRedirection
    {
        int mFrom;  // --> 2>&1
        int mTo;    //     2>&1 <--
    };
    
public:
    //! execution flags
    enum EOptions
    {
        OPTION_NONE=0,
        DETACH=(1<<0),      //!< the process starts detached from its parent
        TERMINAL=(1<<2),    //!< run in a terminal
        SHELL=(1<<3),       //!< run in a shell
        SEARCH_PATH=(1<<4), //!< search in the file for cmd
        SET_TTY_PGRP=(1<<5) //!< set the process group of the controlling terminal to that of the process
    };
    DECLARE_FRIEND_ENUM_BITOPS(EOptions)

    enum EStatus
    {
        STATUS_NOTLAUNCHED,
        STATUS_RUNNING,
        STATUS_STOPPED,
        STATUS_TERMINATED
    };
    
    static const pid_t GROUP_INHERIT=-1;
    static const pid_t GROUP_OWN=-2;

//! \name constructor and destructor
//@{
    //! default constructor
    CProcess();

    //! sets the command and flags for the process

    //! It doesn't run the process, just sets up the object
    CProcess(const std::string &pCmd,EOptions pOptions=OPTION_NONE);
    CProcess(const std::string &pCmd,const std::vector<std::string> &pArgs,EOptions pOptions=OPTION_NONE);
    CProcess(const io::CURI &pCmd,const std::vector<std::string> &pArgs,EOptions pOptions=OPTION_NONE);
    CProcess(const std::vector<std::string> &pCmd,EOptions pOptions=OPTION_NONE);

    //! destructor
    ~CProcess();

    //! returns the command name
    const std::string &GetCmd()     {   return mCmd;    }
    //! returns the command name
    const std::string &GetCmdArg()     {   return mCmdArg.empty()?mCmd:mCmdArg;    }
    //! sets the command name
    void SetCmd(const std::string &pCmd);
    //! sets the command name and args
    void SetCmd(const std::string &pCmd,const std::vector<std::string> &pArgs);
    //! sets the command name and args
    void SetCmd(const io::CURI &pCmd,const std::vector<std::string> &pArgs);
    //! sets the command name and args
    void SetCmd(const std::vector<std::string> &pArgs);
    //! sets the command name as passed in the first argument
    void SetCmdArg(const std::string &pCmdArg);
    //! sets execution options
    void SetOptions(EOptions pFlags);
    void SetOption(EOptions pOption)    {   SetOptions(mOptions|pOption);   }
    //! sets working directory
    void SetWorkingDir(const io::CURI &pURI);
    //! sets the process group of the forked process
    void SetProcessGroup(pid_t pProcessGroup);
    
    //! remove previously added arguments for the process
    void ClearArgs();
    //! adds a single argument
    void AddArg(const std::string&);
    //! adds a vector of arguments
    void AddArgs(const std::vector<std::string> &pArgs);

    //! adds a redirection (equivalent to pFrom>&pTo in shell)
    void AddRedirection(int pFrom,int pTo);
    //! adds a file descriptor to close in the parent
    void AddParentClose(int pFD);
    
    //!gets a stream to the stderr stream of the child process
    io::PFDStream GetErr();
    //!gets a stream to the stdout stream of the child process
    io::PFDStream GetOut();
    //!gets a stream to the stdin stream of the child process
    io::PFDStream GetIn();
    //!gets a stream to both the stdout and stderr streams of the child process
    io::PFDStream GetOutErr();

    //! launches the child process
    void Launch();

    //! returns the current status of the process
    EStatus GetStatus()     {   return mStatus;     }
    //! integer value returned by the process
    int GetExitCode()             {   return mExitCode;         }
    //! returns true if the process ended because it was signalled
    bool Signalled()            {   return mExitCode<0; }
    //! returns the signal that caused the process to end
    int GetExitSignal()         {   return -mExitCode;  }
    //! process ID of the child
    int GetPID()                {   return mPID;            }
    //! waits for process completion
    void Wait(bool pCont=true);
    //! checks if the process status has changed
    bool Check();

//! \name static helper functions
//@{
    //! runs the process pCmd and waits until it completes
    static int Run(const std::string &pCmd);
    //! runs the process and gets its output

    //! \todo implement this
    static int RunAndGetOutput(const std::string &pCmd,const io::CURI &pWD,
                    std::string &pOut,std::string &pErr);
//@}

private:
    void DoChild();
    void DoParent();

    void UpdateStatus(int pStatus);
    
public:
    //! waits for one process to complete or change status
    static CProcess *WaitOne(bool pCont=true);

    //! checks if one process completed or changed status
    static CProcess *CheckOne();

    //! checks if any process completed or changed status
    static void CheckAll();

    USignal<void(int)> sExited;
    USignal<void()> sStopped;
    USignal<void()> sContinued;

protected:
    std::string mCmd;
    std::string mCmdArg;
    std::vector<std::string> mArgs;
    std::vector<SRedirection> mRedirections;
    std::vector<int> mParentClose;
    io::CURI mWD;
    EOptions mOptions;

    pid_t mPID;
    EStatus mStatus;
    int mExitCode;
    
    pid_t mProcessGroup;

    static std::map<pid_t,CProcess*> mAllProcesses;

    friend class CEventLoop;
};

///@}

/*namespace buola*/ }

#endif
